查看原文
其他

如何移植 Android JsBridge 到鸿蒙

鸿洋
2024-08-24

The following article is from MobileDeveloper Author MobileDeveloper

相信大多数小伙伴的项目都已经有了线上稳定运行的 JsBridge 方案,那么对于鸿蒙来说,最好的方案肯定是不需要前端同学的改动,就可以直接运行,这个兼容任务就得我们自己来做了。

关于 JsBridge 的通信原理,在 这篇文章 中已经介绍过了,现在主流的技术方案有 拦截 URL 和 对象注入 两种,我们分别看一下如何在鸿蒙上实现。

1拦截 URL


在安卓上,拦截 URL 这个技术方案的代表作一定是 https://github.com/lzyzsd/JsBridge ,相信有不少小伙伴都使用了这个开源库。
我这里就以该开源库为例,介绍一下如何在鸿蒙上无缝迁移。
首先,在页面加载完成后注入通信需要的 JS 代码。在 Android 中,是 WebViewClient.onPageFinished(),在鸿蒙中对应 Web组件的 onPageEnd()方法。
Web({ src: this.url, controller: this.controller })
  .onPageEnd(() => {
        this.onPageEnd()
        BridgeUtil.webViewLoadLocalJs(getContext(), this.controller, BridgeUtil.toLoadJs)
      })


鸿蒙中本地资源文件放在 resouce/rawfile 目录下,通过以下代码读取:
rawFile2Str(context: Context, file: string): string {
    try {
      let data = context.resourceManager.getRawFileContentSync(file)
      let decoder = util.TextDecoder.create("utf-8")
      let str = decoder.decodeWithStream(data, { stream: false })
      return str
    } catch (e) {
      return ""
    }
  }


读取到的 JS 代码,通过系统能力动态执行。在 Android 中,通过 WebView.loadUrl() 或者 WebView.evaluateJavaScript() 来实现。在鸿蒙中,对应的是 WebviewController.runJavaScriptExt()
webViewLoadLocalJs(context: Context, controller: WebviewController, path: string) {
    let jsContent = BridgeUtil.rawFile2Str(context, path)
    controller.runJavaScriptExt(BridgeUtil.JAVASCRIPT_STR + jsContent, (err, result) => {
      ...
    })
  }


JS 代码注入完成后,就是核心的拦截 URL 了。在 Android 中,通过 WebViewClient.shouldOverrideUrlLoading() 实现,看一下具体的代码:
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    try {
        url = URLDecoder.decode(url, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { 
        webView.handlerReturnData(url);
        return true;
    } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
        webView.flushMessageQueue();
        return true;
    } else {
        return super.shouldOverrideUrlLoading(view, url);
    }
}


拦截到所有的 URL,判断是否是 H5 通过 iFrame.src 发送的指定特征的 URL,来完成通信流程。
在鸿蒙中,对应的是 Web 组件的 onInterceptRequest()方法。
Web({ src: this.url, controller: this.controller })
  .onInterceptRequest((event) => {
        if (event) {
          let url = event.request.getRequestUrl()
          if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {
            this.ytoJsBridge.handlerReturnData(url)
          } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
            this.ytoJsBridge.flushMessageQueue()
          } else {
            return null
          }
        }
        return null
      })


核心逻辑就这样,剩下的工作量就是苦逼的翻译代码。好在代码量并不大,大概五六个文件。
移植过程中,也踩了一些坑,印象最深的是 ArkTs 中关于接口的写法。
export interface CallBackFunction {
 onCallBack(data: string): void
}


这是在 Java/Kotlin 中很常见的一种写法,顺手在 ArkTs 也这么写,但是在使用过程中尝试去写实现的时候就犯了难。如果直接按照传统的前端写法:
let responseFunction: CallBackFunction
if (callBackId != undefined) {
  responseFunction = {
    onCallBack: (data: string): void => {
      ...
    }
  }
}


你会得到一个 lint 错误 Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)
你可以使用箭头函数来解决这个问题。
export interface CallBackFunction {
  onCallBack: (data: string) => void
  // onCallBack(data: string): void
}


这也是 ArkTs 目前比较割裂的地方,基于 TS,但是禁用了很多特性。

设想一下如果可以继续兼容 Java/Kotlin,那么这篇文章都不会存在了,压根不存在迁移成本,海量移动端类库无缝衔接......

2对象注入


对象注入在 Android WebView 中的实现是 WebView.addJavascriptInterface(Object object, String name) 方法 。
addJavascriptInterface(JsBridge(this@MainActivity, webView), "JsBridge")

class JsBridge(private val activity: Activity, private val webView: WebView) {  

    @JavascriptInterface  
    fun webCallNative(message: String) {  
        Log.e("JsBridge""webCallNative: ${Thread.currentThread().name}")  
        Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()  
    }  
}


在鸿蒙中,可以通过 Web 组件的 javaScriptProxy() 方法,或者 WebviewController.registerJavaScriptProxy() 方法。
Web({ src: this.url, controller: this.controller })
    .javaScriptAccess(true)
    .javaScriptProxy({
      object: this.testObj,
      name: "objName",
      methodList: ["test""toString"],
      controller: this.controller,
})


这种方式只支持注入一个对象,如果需要注入多个对象,要用 WebviewController.registerJavaScriptProxy()
this.controller.registerJavaScriptProxy(this.testObjtest, "objName", ["test""toString""testNumber""testBool"]);
this.controller.registerJavaScriptProxy(this.webTestObj, "objTestName", ["webTest""webString"]);


这个方法的调用时机需要注意,必须发生在 controller 和 Web 组件绑定之后,建议放在 Web.onPageEnd()。注册之后需要调用 WebviewController.refresh() 才会生效。

3总结


一入鸿蒙深似海,波涛汹涌无尽头。
云涛翻滚遮日月,雾霭弥漫掩星楼。
仙禽异兽齐飞舞,灵草神木共清幽。
鸿蒙奥秘难穷尽,探寻真道意未休。
Write by 文心一言,如有雷同,请...


最后友情推荐一下 QCon 的鸿蒙会议,这周四-北京-线下-免费,感兴趣可以扫码报名,会议人数是有限制的,没法保证报名一定能成功,先到先得哈。


推荐阅读

Android  绘制缓冲实现「黑客帝国代码雨」
Android稳定性:OOM原理解析
Android 性能:FD监控实现原理


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存